<script setup lang="ts">
import { ref, computed, onMounted, nextTick } from 'vue'

// 导航到指定页面
const navigateToPage = (pageKey: string, serverIp?: string) => {
  const event = new CustomEvent('navigate', { detail: { page: pageKey, serverIp } })
  window.dispatchEvent(event)
}

// 点击服务器卡片，跳转到打击效果评估页面并打开对应WebSSH
function handleServerClick(server: SshServer) {
  if (server.ip) {
    console.log('[ControlCenter] 点击服务器:', server.name, server.ip)
    navigateToPage('attack_effect_monitor', server.ip)
  }
}

// 攻击流程步骤
const attackSteps = [
  {
    id: 'survey',
    title: '攻击资源测绘',
    description: '扫描和识别可用的攻击资源，收集目标网络信息。',
    icon: '🔍',
    color: 'blue',
    pageKey: 'survey'
  },
  {
    id: 'topology',
    title: '链型构建优化',
    description: '设计攻击路径，构建攻击链，优化攻击策略。',
    icon: '🔗',
    color: 'purple',
    pageKey: 'topology_designer'
  },
  {
    id: 'effect',
    title: '打击效果评估',
    description: '实时监控攻击效果，评估打击成果与连锁影响。',
    icon: '📊',
    color: 'orange',
    pageKey: 'attack_effect_monitor'
  }
]

function handleStepClick(step: typeof attackSteps[0]) {
  navigateToPage(step.pageKey)
}

// WebSSH 服务器与状态
type ServerStatus = 'connecting' | 'online' | 'offline' | 'error'

type SshServer = {
  name: string
  ip: string
  status: ServerStatus
}

const sshServers = ref<SshServer[]>([])
const loadingServers = ref(false)

// 计算不同角色的服务器
const scanServer = computed(() =>
  sshServers.value.find(s => s.name.includes('扫描服务器'))
)
const authorityServer = computed(() =>
  sshServers.value.find(s => s.name.includes('权威服务器'))
)
// 获取所有非扫描和权威服务器的服务器（即靶标服务器）
const victimServers = computed(() => {
  return sshServers.value.filter(s => {
    const isScan = s.name.includes('扫描服务器')
    const isAuthority = s.name.includes('权威服务器')
    return !isScan && !isAuthority
  })
})

function getStatusLabel(status: ServerStatus): string {
  switch (status) {
    case 'online': return '在线'
    case 'offline': return '离线'
    case 'connecting': return '连接中...'
    case 'error': return '错误'
    default: return '未知'
  }
}

function getStatusClass(status: ServerStatus): string {
  return status
}

// 测试 WebSSH 连接
function testWebSSHConnection(server: SshServer) {
  server.status = 'connecting'
  try {
    const wsPath = `/webssh?ip=${encodeURIComponent(server.ip)}`
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
    const fullWsUrl = `${protocol}//${window.location.host}${wsPath}`
    const socket = new WebSocket(fullWsUrl)
    const timeout = window.setTimeout(() => {
      try { socket.close() } catch {}
      if (server.status === 'connecting') server.status = 'offline'
    }, 8000)

    socket.onopen = () => {
      window.clearTimeout(timeout)
      server.status = 'online'
      socket.close()
    }
    socket.onerror = () => {
      window.clearTimeout(timeout)
      if (server.status === 'connecting') server.status = 'error'
    }
    socket.onclose = () => {
      window.clearTimeout(timeout)
      if (server.status === 'connecting') server.status = 'offline'
    }
  } catch (error) {
    console.error('[ControlCenter] WebSSH 连接测试失败:', error)
    server.status = 'error'
  }
}

async function loadSshServers() {
  loadingServers.value = true
  try {
    // 模拟数据，如果没有后端可删除此注释直接用真实接口
    const response = await fetch('/get/ssh_servers_info', {
      method: 'GET',
      headers: { Accept: 'application/json' }
    })
    if (!response.ok) throw new Error(`获取SSH服务器信息失败`)
    const data = await response.json()
    sshServers.value = (data || []).map((item: any) => ({
      name: (item.server_name || '未知服务器').replace(/受害/g, '受攻击').replace(/受攻击服务器/g, '靶标服务器'),
      ip: item.ip_address,
      status: 'connecting' as ServerStatus
    }))
    sshServers.value.forEach(server => testWebSSHConnection(server))
  } catch (error) {
    console.error('[ControlCenter] 获取SSH服务器信息失败:', error)
    sshServers.value = []
  } finally {
    loadingServers.value = false
  }
}

onMounted(async () => {
  await loadSshServers()
})
</script>

<template>
  <div class="page">
    <div class="page-header">
      <div class="page-title-block">
        <div class="page-title">
          系统控制总览
          <span class="page-badge">系统级控制</span>
        </div>
        <div class="page-subtitle">
          从攻击资源测绘、攻击链构建到打击效果评估的全流程概览，并展示关键服务器运行与拓扑关系。
        </div>
      </div>
    </div>

    <section class="card flow-card">
      <div class="card-header">
        <div>
          <div class="card-title">
            <div class="card-title-icon">🧭</div>
            攻击全流程
          </div>
          <div class="card-subtitle">
            按步骤引导完成攻击资源测绘、攻击链构建与效果评估。
          </div>
        </div>
      </div>

      <div class="flow-container">
        <div 
          v-for="(step, index) in attackSteps" 
          :key="step.id" 
          class="process-step-card"
          @click="handleStepClick(step)"
        >
          <div class="step-bg-number">0{{ index + 1 }}</div>
          
          <div class="step-content">
            <div class="step-header">
              <div class="step-icon-box" :class="step.color">
                {{ step.icon }}
              </div>
              <div class="step-title-group">
                <h3>{{ step.title }}</h3>
                <span class="step-arrow" v-if="index < attackSteps.length - 1">→</span>
              </div>
            </div>
            <p class="step-desc">{{ step.description }}</p>
          </div>
          
          <div class="step-accent-bar" :class="step.color"></div>
        </div>
      </div>
    </section>

    <section class="card arch-card">
      <div class="card-header">
        <div>
          <div class="card-title">
            <div class="card-title-icon">📡</div>
            系统架构
          </div>
          <div class="card-subtitle">
            展示权威服务器、扫描服务器以及靶标服务器之间的逻辑拓扑结构。
          </div>
        </div>
      </div>

      <div class="arch-layout">
        
        <div class="arch-top-section" v-if="authorityServer">
          <div class="server-card clickable authority-style" @click="handleServerClick(authorityServer)">
            <div class="server-icon soft-blue">
              <svg viewBox="0 0 24 24" class="server-svg" aria-hidden="true">
                <path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
                <path fill="currentColor" d="M12 6c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm0 10c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"/>
              </svg>
            </div>
            <div class="server-info">
              <div class="server-role-tag">核心控制</div>
              <div class="server-name">{{ authorityServer.name }}</div>
              <div class="server-ip">{{ authorityServer.ip || 'IP 未知' }}</div>
              <div class="server-status" :class="getStatusClass(authorityServer.status)">
                <span class="status-dot"></span>
                <span>{{ getStatusLabel(authorityServer.status) }}</span>
              </div>
            </div>
          </div>
        </div>

        <div class="arch-row">
          
          <div class="arch-col left-col">
             <div class="col-label">攻击源侧</div>
             <div class="server-card clickable" v-if="scanServer" @click="handleServerClick(scanServer)">
              <div class="server-icon soft-red">
                <svg viewBox="0 0 24 24" class="server-svg" aria-hidden="true">
                  <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>
                </svg>
              </div>
              <div class="server-info">
                <div class="server-name">{{ scanServer.name }}</div>
                <div class="server-ip">{{ scanServer.ip || 'IP 未知' }}</div>
                <div class="server-status" :class="getStatusClass(scanServer.status)">
                  <span class="status-dot"></span>
                  <span>{{ getStatusLabel(scanServer.status) }}</span>
                </div>
              </div>
            </div>
          </div>

          <div class="arch-col center-col">
            <div class="attack-chain-container">
              <div class="chain-label">
                <span class="chain-icon">⛓️</span> 攻击链拓扑
              </div>
              <div class="attack-chain-diagram">
                <svg viewBox="0 0 800 400" class="attack-chain-svg" preserveAspectRatio="xMidYMid meet">
                  <defs>
                    <linearGradient id="lineGradient" x1="0%" y1="0%" x2="100%" y2="0%">
                      <stop offset="0%" stop-color="#a5b4fc" />
                      <stop offset="100%" stop-color="#6366f1" />
                    </linearGradient>
                  </defs>

                 <g class="lines-group">
                  <!-- 左侧 3 个节点（攻击链起点）发散 -->
                  <line x1="86.84" y1="195.72" x2="233.16" y2="104.28" />
                  <line x1="86.84" y1="200"    x2="230.75" y2="215" />
                  <line x1="86.84" y1="204.28" x2="233.16" y2="295.72" />

                  <!-- 中间第一列 → 中间第二列（8个节点）发散 -->
                  <line x1="249.13" y1="98.48"  x2="473.10" y2="61.15" />
                  <line x1="249.22" y1="100.77" x2="473.02" y2="119.42" />
                  <line x1="249.25" y1="215"    x2="472.75" y2="180" />
                  <line x1="249.25" y1="215"    x2="472.75" y2="240" />
                  <line x1="249.25" y1="215"    x2="472.75" y2="220" />
                  <line x1="249.25" y1="215"    x2="472.75" y2="140" />
                  <line x1="230.75" y1="300"    x2="472.93" y2="275" />
                  <line x1="233.16" y1="304.28" x2="473.10" y2="338.85" />

                  <!-- 中间第二列 8 个节点 → 右侧目标红点（完整 8 条，彻底补齐最上和最下） -->
                  <line x1="486.93" y1="63.73"  x2="706.00" y2="196.00" />  <!-- 最上方（之前缺失） -->
                  <line x1="486.98" y1="122.62" x2="706.50" y2="198.00" />
                  <line x1="487.00" y1="180.00" x2="706.00" y2="200.00" />
                  <line x1="486.93" y1="238.27" x2="706.00" y2="204.00" />
                  <line x1="486.98" y1="220.00" x2="706.00" y2="201.00" />
                  <line x1="486.98" y1="141.00" x2="706.00" y2="198.50" />
                  <line x1="486.93" y1="275.00" x2="706.00" y2="204.00" />
                  <line x1="486.93" y1="338.00" x2="706.00" y2="206.00" />  <!-- 最下方（之前缺失） -->
                </g>

                  <circle cx="80" cy="200" r="8" class="node-dot root" />
                  <circle cx="80" cy="200" r="18" class="pulse-ring blue" />
                  
                  <circle cx="240" cy="100" r="8" class="node-dot mid" />
                  <circle cx="240" cy="220" r="8" class="node-dot mid" />
                  <circle cx="240" cy="300" r="8" class="node-dot mid" />

                  <circle cx="480" cy="60" r="6" class="node-dot secondary" />
                  <circle cx="480" cy="120" r="6" class="node-dot secondary" />
                  <circle cx="480" cy="180" r="6" class="node-dot secondary" />
                  <circle cx="480" cy="240" r="6" class="node-dot secondary" />
                  <circle cx="480" cy="275" r="6" class="node-dot secondary" />
                  <circle cx="480" cy="340" r="6" class="node-dot secondary" />
                  <circle cx="480" cy="220" r="6" class="node-dot secondary" />
                  <circle cx="480" cy="140" r="6" class="node-dot secondary" />

                  <circle cx="720" cy="200" r="12" class="node-dot target" />
                  <circle cx="720" cy="200" r="18" class="pulse-ring" />
                </svg>
              </div>
            </div>
          </div>

          <div class="arch-col right-col">
            <div class="col-label">攻击目标侧</div>
            <div class="victim-stack">
              <div 
                v-for="(server, index) in victimServers" 
                :key="index"
                class="server-card clickable" 
                @click="handleServerClick(server)">
                <div class="server-icon" :class="index === 0 ? 'soft-purple' : 'soft-orange'">
                   <svg viewBox="0 0 24 24" class="server-svg" aria-hidden="true">
                    <path d="M20 18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"/>
                  </svg>
                </div>
                <div class="server-info">
                  <div class="server-name">{{ server.name }}</div>
                  <div class="server-ip">{{ server.ip || 'IP 未知' }}</div>
                  <div class="server-status" :class="getStatusClass(server.status)">
                    <span class="status-dot"></span>
                    <span>{{ getStatusLabel(server.status) }}</span>
                  </div>
                </div>
              </div>
            </div>
          </div>

        </div>
      </div>
    </section>
  </div>
</template>

<style scoped>
/* ---- 全局基础样式 ---- */
.page {
  display: flex;
  flex-direction: column;
  color: #1f2937;
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.page-title {
  font-size: 20px;
  font-weight: 700;
  color: #111827;
  display: flex;
  align-items: center;
  gap: 10px;
}

.page-subtitle {
  font-size: 14px;
  color: #6b7280;
  margin-top: 4px;
}

.page-badge {
  padding: 4px 10px;
  border-radius: 99px;
  font-size: 12px;
  font-weight: 500;
  background: #eef2ff;
  color: #4f46e5;
  border: 1px solid #c7d2fe;
}

.card {
  background: #ffffff;
  border-radius: 16px;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 10px 15px -3px rgba(0, 0, 0, 0.03);
  padding: 20px;
  margin-bottom: 20px;
  border: 1px solid #f3f4f6;
}

.card-header {
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid #f3f4f6;
}

.card-title {
  font-size: 16px;
  font-weight: 700;
  color: #111827;
  display: flex;
  align-items: center;
  gap: 10px;
}

.card-title-icon {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
  color: #fff;
  font-size: 14px;
  box-shadow: 0 4px 6px rgba(99, 102, 241, 0.25);
}

.card-subtitle {
  font-size: 13px;
  color: #9ca3af;
  margin-top: 4px;
  margin-left: 38px;
}

/* ---- 流程卡片 ---- */
.flow-container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 24px;
  padding: 8px 0;
}

.process-step-card {
  position: relative;
  background: #f9fafb;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  padding: 20px;
  cursor: pointer;
  transition: all 0.3s ease;
  overflow: hidden;
}

.process-step-card:hover {
  background: #ffffff;
  transform: translateY(-4px);
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.06);
  border-color: #dbeafe;
}

.step-bg-number {
  position: absolute;
  right: -10px;
  top: -15px;
  font-size: 80px;
  font-weight: 900;
  color: rgba(229, 231, 235, 0.4);
  z-index: 0;
  font-family: 'Segoe UI', sans-serif;
  pointer-events: none;
}

.step-content {
  position: relative;
  z-index: 1;
}

.step-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}

.step-icon-box {
  width: 42px;
  height: 42px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  color: white;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}

.step-icon-box.blue { background: linear-gradient(135deg, #3b82f6, #2563eb); }
.step-icon-box.purple { background: linear-gradient(135deg, #a855f7, #7c3aed); }
.step-icon-box.orange { background: linear-gradient(135deg, #f97316, #ea580c); }

.step-title-group {
  display: flex;
  align-items: center;
  flex: 1;
  justify-content: space-between;
}

.step-title-group h3 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: #1f2937;
}

.step-arrow {
  font-size: 18px;
  color: #d1d5db;
  font-weight: bold;
  transform: translateX(10px);
}

.step-desc {
  font-size: 13px;
  color: #6b7280;
  line-height: 1.6;
  margin: 0;
}

.step-accent-bar {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 0%;
  height: 3px;
  transition: width 0.3s ease;
}
.process-step-card:hover .step-accent-bar { width: 100%; }
.step-accent-bar.blue { background: #3b82f6; }
.step-accent-bar.purple { background: #a855f7; }
.step-accent-bar.orange { background: #f97316; }

/* ---- 系统架构卡片 ---- */
.arch-layout {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.arch-top-section {
  display: flex;
  justify-content: center;
  padding-bottom: 24px;
  position: relative;
}

/* 从权威服务器向下连到攻击链矩形 */
.arch-top-section::after {
  content: '';
  position: absolute;
  bottom: -12px;
  left: 50%;
  transform: translateX(-50%);
  width: 2px;
  height: 40px;
  background: linear-gradient(to bottom, #cbd5e1, #a5b4fc);
  z-index: 0;
}

/* 权威服务器卡片：渐变小卡片+轻微强调 */
.authority-style {
  border: none;
  background: linear-gradient(135deg, #e0f2fe 0%, #eef2ff 40%, #ffffff 100%);
  box-shadow: 0 10px 25px rgba(129, 140, 248, 0.25);
  min-width: 260px;
}

.server-role-tag {
  font-size: 10px;
  color: #4f46e5;
  background: rgba(79, 70, 229, 0.08);
  padding: 2px 6px;
  border-radius: 999px;
  width: fit-content;
  margin-bottom: 4px;
}

.arch-row {
  display: flex;
  gap: 20px;
  align-items: center;
}

.arch-col {
  display: flex;
  flex-direction: column;
}

.left-col,
.right-col {
  flex: 0 0 240px;
  justify-content: center;
  gap: 12px;
}

.center-col {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.col-label {
  font-size: 12px;
  font-weight: 600;
  color: #94a3b8;
  text-align: center;
  margin-bottom: 8px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

/* ---- 攻击链矩形 & 连线 ---- */
.attack-chain-container {
  position: relative;
  width: 100%;
  max-width: 860px;
  margin: 0 auto;
  padding: 14px 18px;
  border-radius: 16px;
  background: rgba(241, 245, 249, 0.8);  /* 浅色矩形区域 */
  border: 1px dashed #cbd5e1;
  box-shadow: 0 8px 18px rgba(148, 163, 184, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 220px;   /* 比之前矮很多，只包裹拓扑 */
  overflow: hidden;
}

/* 左右两侧连线：从矩形中线延伸到两边服务器 */
.attack-chain-container::before,
.attack-chain-container::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 80px;
  height: 2px;
  transform: translateY(-50%);
  background: linear-gradient(to right, #cbd5e1, #818cf8);
  opacity: 0.9;
  pointer-events: none;
}

.attack-chain-container::before {
  left: -80px;
  transform-origin: right center;
}

.attack-chain-container::after {
  right: -80px;
  transform-origin: left center;
}

/* 攻击链标签：放在矩形左上角的小标签 */
.chain-label {
  position: absolute;
  top: 10px;
  left: 14px;
  background: #ffffff;
  padding: 4px 10px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 600;
  color: #475569;
  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
  border: 1px solid #e2e8f0;
  display: flex;
  align-items: center;
  gap: 6px;
}

.chain-icon { font-size: 12px; }

.attack-chain-diagram {
  width: 100%;
  height: 100%;
  display: flex;                 /* 重新显示拓扑图 */
  align-items: center;
  justify-content: center;
}

/* SVG 缩放到刚好填满矩形，不会太高 */
.attack-chain-svg {
  width: 100%;
  height: auto;
  max-height: 200px;            /* 控制整体高度，让矩形更紧凑 */
}

.lines-group line {
  stroke: url(#lineGradient);
  stroke-width: 2;
  opacity: 0.6;
  stroke-linecap: round;
}

.node-dot {
  fill: #ffffff;
  stroke-width: 2.5;
}
.node-dot.root { stroke: #3b82f6; fill: #eff6ff; animation: nodeBlink 1.4s infinite; }
.node-dot.mid { stroke: #6366f1; }
.node-dot.secondary { stroke: #8b5cf6; r: 5; stroke-width: 2; }
.node-dot.target { fill: #ef4444; stroke: #fee2e2; stroke-width: 4; }

.pulse-ring {
  fill: none;
  stroke: #ef4444;
  stroke-width: 2;
  opacity: 0.5;
  animation: pulse 2s infinite;
}
.pulse-ring.blue { stroke: #3b82f6; }

@keyframes pulse {
  0% { r: 12; opacity: 0.8; stroke-width: 2; }
  100% { r: 24; opacity: 0; stroke-width: 0; }
}

@keyframes nodeBlink {
  0% { opacity: 1; stroke-width: 2.5; }
  50% { opacity: 0.5; stroke-width: 4; }
  100% { opacity: 1; stroke-width: 2.5; }
}

.victim-stack {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* ---- 服务器卡片：渐变小卡片风格 ---- */
.server-card {
  position: relative;
  background: radial-gradient(circle at top left, #e0f2fe 0%, #eef2ff 45%, #ffffff 100%);
  border-radius: 18px;
  padding: 14px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  transition: all 0.25s ease;
  border: none;
  box-shadow: 0 10px 26px rgba(148, 163, 184, 0.35);
}

.server-card::after {
  content: '';
  position: absolute;
  inset: 1px;
  border-radius: 17px;
  background: linear-gradient(to bottom right, rgba(255,255,255,0.8), rgba(255,255,255,0.1));
  mix-blend-mode: screen;
  pointer-events: none;
}

.server-card.clickable:hover {
  transform: translateY(-3px) scale(1.01);
  box-shadow: 0 16px 32px rgba(129, 140, 248, 0.4);
}

/* 左边圆角渐变图标 */
.server-icon {
  width: 44px;
  height: 44px;
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  flex-shrink: 0;
}

.soft-blue  { background: linear-gradient(135deg, #4f46e5, #3b82f6); }
.soft-red   { background: linear-gradient(135deg, #f97373, #ef4444); }
.soft-purple{ background: linear-gradient(135deg, #c4b5fd, #9333ea); }
.soft-orange{ background: linear-gradient(135deg, #fbbf24, #f97316); }

.server-svg {
  width: 24px;
  height: 24px;
  fill: currentColor;
}

.server-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
  position: relative;
  z-index: 1;
}

.server-name {
  font-size: 14px;
  font-weight: 600;
  color: #111827;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.server-ip {
  font-size: 12px;
  color: #64748b;
  font-family: 'Monaco', monospace;
  margin-top: 2px;
}

.server-status {
  margin-top: 6px;
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 500;
}

.status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
}

.server-status.online {
  color: #16a34a;
}
.server-status.online .status-dot {
  background: #16a34a;
  box-shadow: 0 0 6px rgba(22, 163, 74, 0.9);
}

.server-status.offline {
  color: #9ca3af;
}
.server-status.offline .status-dot {
  background: #9ca3af;
}

.server-status.connecting {
  color: #f59e0b;
}
.server-status.connecting .status-dot {
  background: #f59e0b;
  animation: blink 1s infinite;
}

.server-status.error {
  color: #ef4444;
}
.server-status.error .status-dot {
  background: #ef4444;
}

@keyframes blink {
  50% { opacity: 0.4; }
}

/* ---- 响应式 ---- */
@media (max-width: 1100px) {
  .flow-container { grid-template-columns: 1fr; }

  .arch-row {
    flex-direction: column;
    gap: 24px;
  }

  .left-col,
  .right-col {
    flex: auto;
    flex-direction: row;
  }

  .victim-stack {
    flex-direction: row;
    flex: 1;
  }

  .attack-chain-container {
    max-width: 720px;
    min-height: 200px;
  }

  .attack-chain-container::before,
  .attack-chain-container::after {
    width: 60px;
  }
}
</style>